package logic;

import dao.TurnoDAO;
import dto.HorarioDTO;
import dto.PacienteDTO;
import dto.TarifaDTO;
import dto.TurnoDTO;
import fs.Data;
import java.io.File;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import util.Email;
import util.InputHelper;

/**
 *
 * @author the_rub
 */
public class Turno {

  // enums in Java suck… well, Java sucks ;)
  public static final String libre = "Libre";
  public static final String asignado = "Asignado";
  public static final String anulado = "Anulado";
  public static final String asistido = "Asistido";
  public static final String ausente = "Ausente";

  private int id;
  private LocalDateTime fecha;
  private int idMedico;
  private int idPaciente;
  private String estado; //ver más arriba
  private boolean sobreturno;
  private int idObraSocialConsulta;;
  private int idPlanConsulta;
  private int nAfiliadoConsulta;
  private double montoConsulta;
  private LocalDateTime fechaAsignacion;
  private int idUserAsignacion;

  public Turno(int id, LocalDateTime fecha, int idMedico, int idPaciente, String estado, boolean sobreturno, int idObraSocialConsulta, int idPlanConsulta, int nAfiliadoConsulta, double montoConsulta, LocalDateTime fechaAsignacion, int idUserAsignacion) {
    this.id = id;
    this.fecha = fecha;
    this.idMedico = idMedico;
    this.idPaciente = idPaciente;
    this.estado = estado;
    this.sobreturno = sobreturno;
    this.idObraSocialConsulta = idObraSocialConsulta;
    this.idPlanConsulta = idPlanConsulta;
    this.nAfiliadoConsulta = nAfiliadoConsulta;
    this.montoConsulta = montoConsulta;
    this.fechaAsignacion = fechaAsignacion;
    this.idUserAsignacion = idUserAsignacion;
  }

  public Turno(TurnoDTO t) {
    this.id = t.getId();
    this.fecha = t.getFecha();
    this.idMedico = t.getIdMedico();
    this.idPaciente = t.getIdPaciente();
    this.estado = t.getEstado();
    this.sobreturno = t.isSobreturno();
    this.idObraSocialConsulta = t.getIdObraSocialConsulta();
    this.idPlanConsulta = t.getIdPlanConsulta();
    this.nAfiliadoConsulta = t.getNAfiliadoConsulta();
    this.montoConsulta = t.getMontoConsulta();
    this.fechaAsignacion = t.getFechaAsignacion();
    this.idUserAsignacion = t.getIdUserAsignacion();
  }

  public static LocalDateTime UltimoTurnoGenerado() throws SQLException {
    return TurnoDAO.createDAO().LastLocalDateTime();
  }

  public static LocalDateTime UltimoTurnoGeneradoDelMedico(int idMedico) throws SQLException {
    return TurnoDAO.createDAO().LastLocalDateTimeFromMedico(idMedico);
  }

  // Los turnos no se guardan, se generan. Así que public void Save() no existe.
  public static void GenerarTurnosLibres(int idMedico, LocalDateTime fechaStart, LocalDateTime fechaEnd, int duracionDelTurno) throws SQLException {
    ArrayList<HorarioDTO> hdtos = Horario.SearchByMedicoID(idMedico);
    fechaEnd = fechaEnd.plusDays(1);
    for (LocalDateTime d = fechaStart; d.isBefore(fechaEnd); d = d.plusDays(1)) {
      for (HorarioDTO h : hdtos) {
        if (h.getDia().equals(d.getDayOfWeek())) {
          GenerarTurnosLibresPorDia(idMedico, d.with(h.getHoraDesde()), d.with(h.getHoraHasta()), duracionDelTurno);
        }
      }
    }
  }

  private static void GenerarTurnosLibresPorDia(int idMedico, LocalDateTime diaLaboralStart, LocalDateTime diaLaboralEnd, int duration) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    dao.Save(new TurnoDTO(-1, diaLaboralStart, idMedico, 0, libre, true, null, null, -1, -1.0, LocalDateTime.parse("1970-01-01T00:00"), -1));
    for (LocalDateTime f = diaLaboralStart; f.isBefore(diaLaboralEnd); f = f.plusMinutes(duration)) {
      dao.Save(new TurnoDTO(-1, f, idMedico, 0, libre, false, null, null, -1, -1.0, LocalDateTime.parse("1970-01-01T00:00"), -1));
    }
  }

  public TurnoDTO AsignarTurnoToPacienteWithUserLog(int idPaciente, int idUserAsignacion) throws SQLException {
    if (!estado.equals(libre)) {
      throw new IllegalArgumentException("?turno no asignable\n");
    }

    determinarTipoDeAtencionDelPaciente(idPaciente);
    registrarTarifaDeLaConsulta();

    if (isSobreturno()) {
      TurnoDTO dto = new TurnoDTO(-1, fecha, idMedico, this.idPaciente = idPaciente, estado = asignado, true, idObraSocialConsulta, idPlanConsulta, nAfiliadoConsulta, montoConsulta, fechaAsignacion = InputHelper.FromOldDateToNew(new Date()), this.idUserAsignacion = idUserAsignacion);
      TurnoDAO.createDAO().Save(dto);
      return dto;
    }

    /*
    // Con esto se puede comprobar la concurrencia del sistema, ahora funca joya.
    try {
      Thread.sleep(10000);
    } catch (InterruptedException e) {
    }
    */

    return edit(this.idPaciente = idPaciente, libre, estado = asignado, idObraSocialConsulta, idPlanConsulta, nAfiliadoConsulta, montoConsulta, fechaAsignacion = InputHelper.FromOldDateToNew(new Date()) , this.idUserAsignacion = idUserAsignacion);
  }

  public static boolean HayTurnosLibresDelMedicoEnFecha(int idMedico, LocalDate fecha) throws SQLException {
    ArrayList<TurnoDTO> turnos = SearchByFechaMedico(fecha, idMedico);
    Turno t;
    for (TurnoDTO dto : turnos) {
      t = new Turno(dto);
      if (t.IsLibre() && !t.isSobreturno()) {
        return true;
      }
    }
    return false;
  }

  public TurnoDTO RegistrarAsistencia() throws SQLException {
    if (fecha.toLocalDate().isAfter(LocalDate.now())) {
      throw new IllegalArgumentException("?turno en el futuro\n");
    }

    determinarTipoDeAtencionDelPaciente(idPaciente);
    registrarTarifaDeLaConsulta();

    return edit(idPaciente, asignado, estado = asistido, idObraSocialConsulta, idPlanConsulta, nAfiliadoConsulta, montoConsulta, fechaAsignacion, idUserAsignacion); // los dos últimos parámetros no se usan, pero no importa
  }

  public TurnoDTO RegistrarAusencia() throws SQLException {
    return editEstado(asignado, estado = ausente);
  }

  public static void RegistrarTurnosViejosComoAusentes() throws SQLException {
    LocalDate yesterday = LocalDate.now().plusDays(-1);
    ArrayList<TurnoDTO> dtos = SearchAsignadosHasta(yesterday);
    for (TurnoDTO t : dtos) {
      new Turno(t).RegistrarAusencia();
    }
  }

  // Cancelar turno por deseo del paciente.
  // El turno se libera para otro paciente.
  public TurnoDTO LiberarTurno() throws SQLException {
    if (isSobreturno()) {
      TurnoDAO.createDAO().DeleteSobreturno(id);
      return null;
    }

    return edit(idPaciente = 0, asignado, estado = libre, null, null, -1, -1.0, LocalDateTime.parse("1970-01-01T00:00"), -1);
  }

  // Turno anulado por el médico, por feriado, por paro y/o piquete.
  // El turno no se libera, queda anulado.
  public TurnoDTO AnularTurno() throws SQLException {
    TurnoDTO t = estado.equals(libre) ? editEstado(libre, estado = anulado) : estado.equals(asignado) ? editEstado(asignado, estado = anulado) : null;
    return t; // si no anula, devuelve null
  }

  private void determinarTipoDeAtencionDelPaciente(int idPaciente) throws SQLException {
    Medico m = new Medico(Medico.SearchByID(idMedico));
    PacienteDTO p = Paciente.SearchByID(idPaciente);

    idObraSocialConsulta = p.getIdObraSocial();
    idPlanConsulta = p.getIdPlanObraSocial();
    nAfiliadoConsulta = p.getNAfiliado();

    if (!m.CoberturaMatch(idObraSocialConsulta, idPlanConsulta)) {
      idObraSocialConsulta = idPlanConsulta = nAfiliadoConsulta = 0;
    }
  }

  private void registrarTarifaDeLaConsulta() throws SQLException {
    Medico m = new Medico(Medico.SearchByID(idMedico));
    TarifaDTO tarifa = Tarifa.SearchByObraSocialEspecialidad(idObraSocialConsulta, m.getIdEspecialidad());
    montoConsulta = tarifa.getPrecio();
  }

  public static void MandarComprobantesViaEmail() {
    File d = new File(Data.SENZU_COMPROBANTES);
    File[] files = d.listFiles();

    String[] archivo = new String[1];
    String[] archivoNombre = new String[1];

    for (File f : files) {
      try {
        TurnoDTO t = Turno.SearchByID(Integer.parseInt(f.getName().replace(".pdf", "")));

        if (t == null || !t.getEstado().equals(asignado)) {
          f.delete();
          return;
        }

        PacienteDTO p = Paciente.SearchByID(t.getIdPaciente());

        if (LocalDate.now().isAfter(t.getFecha().toLocalDate().plusDays(-4))) {
          archivo[0] = f.getAbsolutePath();
          archivoNombre[0] = f.getName();
          new Email(
            p.getEmail(),
            String.format("Clínica Senzu: Recordatorio de turno"),
            String.format("Estimado %s %s:\n\nLe recordamos que tiene un turno para el día %s a las %s hs.\nAdemás, le enviamos un archivo adjunto con el comprobante del turno.\n\nSaludos cordiales.\nClínica Senzu.", p.getNombre(), p.getApellido(), t.getFecha().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")), t.getFecha().toLocalTime()),
            archivo, archivoNombre
          ).Send();

          f.delete();
        }
      } catch (Exception e) {
        System.out.printf("?comprobante email issue\n");
        // continue;
      }
    }
  }

  private TurnoDTO edit(int idPaciente, String estadoViejo, String estadoNuevo, Integer idObraSocialConsulta, Integer idPlanConsulta, int nAfiliadoConsulta, double montoConsulta, LocalDateTime fechaAsignacion, int idUserAsignacion) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.Edit(id, idPaciente, estadoViejo, estadoNuevo, idObraSocialConsulta, idPlanConsulta, nAfiliadoConsulta, montoConsulta, fechaAsignacion, idUserAsignacion);
  }

  private TurnoDTO editEstado(String estadoViejo, String estadoNuevo) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.EditEstado(id, estadoViejo, estadoNuevo);
  }

  public static TurnoDTO SearchByID(int id) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.TurnoDTO(id);
  }

  public static ArrayList<TurnoDTO> SearchByFechaMedico(LocalDate fecha, int idMedico) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.SearchByFechaMedico(fecha, idMedico);
  }

  public static ArrayList<TurnoDTO> SearchByMedicoID(int idMedico) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.SearchByMedicoID(idMedico);
  }

  public static ArrayList<TurnoDTO> SearchByPacienteID(int idPaciente, String estado) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.SearchByPacienteID(idPaciente, estado);
  }

  public static ArrayList<TurnoDTO> SearchTurnosAsistidosByObraSocialFromDateToDate(int idObraSocial, LocalDate fechaDesde, LocalDate fechaHasta, boolean orderByMedico) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.SearchTurnosAsistidosByObraSocialFromDateToDate(idObraSocial, fechaDesde, fechaHasta, orderByMedico);
  }

  public static ArrayList<TurnoDTO> SearchAsignadosHasta(LocalDate fecha) throws SQLException {
    TurnoDAO dao = TurnoDAO.createDAO();
    return dao.SearchAsignadosHasta(fecha);
  }

  // Existe un sobreturno que siempre es un turno libre.
  // Este método no lo cuenta.
  public static int CantidadDeTurnosLibres(ArrayList<TurnoDTO> dtos) {
    int count = 0;
    for (TurnoDTO t : dtos) {
      if (t.getEstado().equals(libre) && !t.isSobreturno()) {
        count++;
      }
    }
    return count;
  }

  public static boolean SePuedenSacarSobreturnos(ArrayList<TurnoDTO> dtos) {
    for (TurnoDTO t : dtos) {
      if (t.isSobreturno() && t.getEstado().equals(libre)) {
        return true;
      }
    }
    return false;
  }

  // FIXXXME!!!
  public boolean isInTheFuture() {
    return true;
  }

  public boolean IsLibre() {
    return estado.equals(libre);
  }

  public boolean IsAsignado() {
    return estado.equals(asignado);
  }

  public boolean IsAnulado() {
    return estado.equals(anulado);
  }

  public boolean IsAsistido() {
    return estado.equals(asistido);
  }

  public boolean IsAusente() {
    return estado.equals(ausente);
  }

  public boolean isSobreturno() {
    return sobreturno;
  }

  public int getId() {
    return id;
  }

  public LocalDateTime getFecha() {
    return fecha;
  }

  public int getIdMedico() {
    return idMedico;
  }

  public int getIdPaciente() {
    return idPaciente;
  }

  public String getEstado() {
    return estado;
  }

  public int getIdObraSocialConsulta() {
    return idObraSocialConsulta;
  }

  public int getIdPlanConsulta() {
    return idPlanConsulta;
  }

  public int getNAfiliadoConsulta() {
    return nAfiliadoConsulta;
  }

  public double getMontoConsulta() {
    return montoConsulta;
  }

  public LocalDateTime getFechaAsignacion() {
    return fechaAsignacion;
  }

  public int getIdUserAsignacion() {
    return idUserAsignacion;
  }

}
